package spider

import (
	"context"
	"errors"
	"fmt"
	"gdei_healthy_colly/config"
	"gdei_healthy_colly/log"
	"gdei_healthy_colly/msg"
	"github.com/robfig/cron/v3"
	"github.com/spf13/cast"
	"go.uber.org/zap"
	"net/http"
	"runtime/debug"
	"strings"
	"time"
)

// 客户端Header相关
const (
	Referer   = "https://tb.gdei.edu.cn/system/main"
	UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
	BaseUrl   = "https://tb.gdei.edu.cn/"
)

// 操作
const (
	opIndex = iota
	opLogin
	opDailyFormGet
	opDailyFormPost
	opDailyFormEdit
)

// 请求信息结构体
type requestMetaData struct {
	url    string
	Method string
}

// URL结合前缀
func (r *requestMetaData) Url() string {
	return BaseUrl + r.url
}

// 请求信息结构体字典
var opMapper = map[uint]*requestMetaData{
	opIndex:         {url: "index", Method: http.MethodGet},
	opLogin:         {url: "login", Method: http.MethodPost},
	opDailyFormGet:  {url: "system/mrcj/add", Method: http.MethodGet},
	opDailyFormPost: {url: "system/mrcj/add", Method: http.MethodPost},
	opDailyFormEdit: {url: "system/mrcj/edit", Method: http.MethodPost},
}

// 全局错误
var (
	LoginRequiredErr = errors.New("login Required")
)

// 响应结构体
type respStruct struct {
	Msg  string
	Code int
}

// 登录
func Login(ctx context.Context, ac Account) (err error) {
	return login(ctx, ac)
}

// 提交每日表单
func PostDailyForm(ctx context.Context, ac Account) (err error) {
	// 如果客户端为空，则初始化
	if ac.GetClient() == nil {
		return fmt.Errorf("client is nil, %s", ac.GetUsername())
	}

	if err = login(ctx, ac); err != nil {
		return
	}

	// 获取每日表单
	values, err := getDailyForm(ctx, ac.GetClient())
	if err != nil {
		log.Logger.Error("[获取表单信息]", zap.Error(err))
		return
	}

	// 设置随机体温
	values.Set("tiwen", randTemperature())

	// 构造表单
	switch strings.EqualFold(values.Get("id"), "") {
	case false:
		err = dailyFormPost(ctx, ac, opDailyFormEdit, values)
	case true:
		err = dailyFormPost(ctx, ac, opDailyFormPost, values)
	}

	if err != nil {
		log.Logger.Error("[提交表单失败]",
			zap.String("ID", ac.GetId()),
			zap.String("Username", ac.GetUsername()),
			zap.Error(err),
		)
		return
	}

	log.Logger.Info("[提交表单成功]",
		zap.String("ID", ac.GetId()),
		zap.String("Username", ac.GetUsername()),
		zap.Any("Values", values))
	return
}

// 定时器
func Timer() {
	c := cron.New()
	log.Logger.Info("[定时器]", zap.Time("运行时间", time.Now()), zap.String("时区", c.Location().String()))
	eid, err := c.AddFunc(config.Cfg.CommitTime, func() {
		// 从配置文件中读取用户
		acs := LoadAccounts()
		for _, v := range acs {
			go func(ac Account) {
				defer func() {
					if err := recover(); err != nil {
						log.Logger.Error("[定时器]",
							zap.Any("崩溃", err),
							zap.ByteString("堆栈信息", debug.Stack()))
						go msg.ServerChanMsg(context.Background(),
							"[GHC崩溃]"+ac.GetUsername(),
							cast.ToString(debug.Stack()))
					}
				}()

				// 错误重试，每次间隔15分钟
				var err error
				for rt := 0; rt < config.Cfg.Retry; rt++ {
					if err = PostDailyForm(context.Background(), ac); err == nil {
						return
					}
					go msg.ServerChanMsg(context.Background(),
						fmt.Sprintf("[GHC]%v打卡失败，正在重试", ac.GetUsername()),
						time.Now().Format(time.RFC3339))
					<-time.After(time.Duration(config.Cfg.RetryDelay) * time.Minute)
				}

				// 如果最终还是失败了则发送失败信息
				if err != nil {
					log.Logger.Info("[定时器重试失败]",
						zap.String("ID", ac.GetId()),
						zap.String("username", ac.GetUsername()),
						zap.Error(err),
					)
					go msg.ServerChanMsg(context.Background(),
						fmt.Sprintf("[GHC定时器重试失败] ID %s / Username %s",
							ac.GetId(), ac.GetUsername()), err.Error())
				}
			}(v)
		}
		// 发送通知
		go msg.ServerChanMsg(context.Background(), "[GHC定时器]正在进行打卡",
			fmt.Sprintf("共%d位用户", len(acs)))

		for _, entry := range c.Entries() {
			log.Logger.Info("[GHC定时器]", zap.Time("下一次执行时间", entry.Schedule.Next(time.Now())))
		}
	})
	if err != nil {
		log.Logger.Error("[定时任务错误]", zap.Error(err))
		return
	}
	log.Logger.Info("[GHC定时器]", zap.Time("下一次执行时间", c.Entry(eid).Schedule.Next(time.Now())))
	c.Run()
}
